Add D256 to GQA-8 two-pass vector attention - #4477
Conversation
RohanGautam
left a comment
There was a problem hiding this comment.
Thanks for another PR!
As with your other one, I think you can include some end to end numbers, and I'll run it on some more machines to verify.
| self.assertTrue(mx.allclose(ref, out, atol=1e-4, rtol=1e-4)) | ||
|
|
||
| @unittest.skipUnless(mx.metal.is_available(), "Metal is not available") | ||
| def test_sdpa_vector_gqa_d256(self): |
There was a problem hiding this comment.
Same concern i had in #4476 , this mainly tests numerics and not necessarily the changes you introduced. For your changes, good benchmarking should be sufficient. But let me know if you disagree!
|
Thanks for taking a look. I tested the model-level decode path with CodeGemma 2B at an 8K context. The kernel benchmark still improves, but the exact base/PR model rerun did not show a stable end-to-end gain: the three paired base/PR ratios were 0.9993x, 1.0093x, and 1.0042x (1.0042x median). I haven't found a workload where the kernel improvement translates into a stable model-level gain, so I'm closing this PR. |
The GQA-8 two-pass kernel already shares K/V loads across query heads for D64 and D128. D256 can use the same kernel with two query heads per simdgroup: its float threadgroup arrays take 16,512 bytes, while four heads would exceed 32 KiB.
This adds that specialization for single-token decode with at least 8192 keys, matching query/value dimensions, no array mask and no sinks. It fits the D256, 8/1-head attention layers in CodeGemma 2B when using an ordinary floating-point KV cache. Quantized KV cache uses a separate path.
The six measured cells below are faster on M5 Max. Other Metal GPUs have not been measured. Two three-pair CodeGemma whole-model screens were inconclusive and do not support a throughput claim. Tests cover all three dtypes, odd key lengths, batch 2 and sliced KV. From
python/tests,MLX_ENABLE_TF32=0 python -m unittest -v test_fast_sdpapassed (28 tests, 2 skipped);uvx pre-commit run --all-filespassed.Times are arm medians; ratios are paired geometric means of main/PR time, with 95% CIs (
MLX_ENABLE_TF32=0). The table was measured atb04aea2a7against5778a97c0; merge commit288906a50was correctness-tested but not retimed.Benchmark reproduction
Measured builds: main
5778a97c0, candidateb04aea2a7; Apple M5 Max, 128 GiB, macOS 27.0 (26A5425a). Build separate source checkouts with identical Release settings and Python bindings under each checkout'spython/directory. Save the script below assdpa_microbench.py.Set
--dtype,--q,--k,--hq,--hkand--batchfor each row. The script reports seconds per call using a four-call dependent chain. Keep other GPU work idle; thermal-limit telemetry was unavailable during these measurements.Use 30 fixed main/main calibration pairs followed by 30 main/PR pairs, alternating package order, with 60 seconds of preconditioning and 30 seconds between batches. The float32 cells used a separate session with five minutes of initial cooling.
Reject arm-median drift above 5% between session halves; allow one retry after 120 seconds of cooling with doubled preconditioning. The float32 k32768 A/B used that retry. Retain all pairs, including flagged outliers, in the paired log-ratio mean and Student-t 95% interval. Do not pool sessions; treat overlap with the calibration interval as unresolved. All six comparisons passed calibration and drift checks.